﻿using CKS.Core.Framework;
using CKS.PLC.Action.Subscription;
using CKS.PLC.Core;
using CKS.PLC.Core.Siemens;
using FreeSql;
using Google.Protobuf.WellKnownTypes;
using ImportPlcPointPosition.Models;
using Infrastructure;
using Infrastructure.Extensions;
using Newtonsoft.Json.Linq;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ZR.Model.Enums;
using ZR.Model.Models.ProductionControl;
using ZR.ServiceCore.Services;

namespace CKS.PLC.Action.System {
    /// <summary>
    /// 执行加工请求方法类
    /// </summary>
    public class StartWorkingAction : PLCAction {

        /// <summary>
        /// 加工请求信号：开始加工，
        /// 验证是否允许加工
        /// </summary>
        /// <param name="value"></param>
        public StartWorkingAction(DecodeValue value) : base(value) {

        }

        public override bool ExecuteBefor(Dictionary<string, object> execParams) {

            if (!WritePalletCodeAndProductTypeToPlc()) {
                WriteLogUI("ExecuteBefor，Return！");
                return false;
            }

            return true;
        }

        /*
            回复信号 
            1：允许加工 
            11：本工位已完成OK；
            12：上工位不合格；
            13：本工位已完成NG；
            14：加工超时；(涂胶机)
            21：绿兔子；22：红兔子；
            99：条码为空
            98：条码检测不合格
            -1：MES系统异常
         */
        public override void Execute(Dictionary<string, object> execParams) {

            using (var ObjectCo = base.FreeSql.Ado.MasterPool.Get()) {
                var conn = ObjectCo.Value;
                var position = DecodeValue.Position;
                var act = conn.Select<CksPlcActions>()
                    .Where(a => a.ActionType == 2
                            && a.PlcId == position.PlcId
                            && a.ActionId == position.ActionId
                            && a.ActionKey == position.ActionKey
                            && a.ReadDB == position.BlockDb
                            )
                    .First();

                if (act == null) {
                    throw new Exception($"加工请求信号未配置此动作编号PLC：{position.WorkStationCode}，DB：{position.BlockDb}，Action：{position.ActionId}");
                }

                //1.获取请求结果
                var result = DecodeValue.Value.ToInt();

                //2.获取回复点位
                var replyPos = DecodeValue.GetReplyPosition(act.WriteDB, act.ActionId, position.ActionKey);

                //3.请求判断1：加工请求信号，0：复位请求信号
                if (result == 1) {

                    //查询工艺节点
                    var technical = conn.Select<CksTechnicalFlowNodes>().Where(a => a.Id == act.BusinessId).First();

                    var prevTechNode = conn.Select<CksTechnicalFlowNodes>().Where(a => a.NodeId == technical.PreNodeId).First();

                    var fields = conn.Select<CksValidationField>()
                        .Where(a => a.BusinessType == 1 && a.BusinessId == act.BusinessId).OrderBy(a => a.Sequence)
                        .ToList();

                    if (!fields.Any(o => o.IsPrimary)) {
                        throw new Exception($"工艺节点，未配置条码验证字段，请检查工艺流程配置");
                    }

                    //获取主条码值
                    var codevalue = GetMainCodeValue();

                    #region 验证工艺流程

                    //a.验证工艺流程，工艺路线未配置，或者当前节点是禁用执行，直接回复信号OK
                    if (technical == null || technical.Nullity) {
                        WriteInt16(replyPos, 1);
                        WriteLogUI($"加工开始");
                        return;
                    }

                    short ErrCode = 0;//回复的错误码
                    string ErrMessge = "";

                    //b.验证前置节点并且前置节点未被禁用
                    if (technical.NeedPrevCheck && prevTechNode != null && !prevTechNode.Nullity) {
                        ErrCode = ValidNode(conn, prevTechNode, true, out ErrMessge);
                        if (ErrCode != 0) {
                            WriteInt16(replyPos, ErrCode);
                            WriteLogUI($"加工失败，{ErrMessge}");
                            return;
                        }
                    }

                    //c.验证当前节点
                    ErrCode = ValidNode(conn, technical, false, out ErrMessge);
                    if (ErrCode != 0) {
                        WriteInt16(replyPos, ErrCode);
                        WriteLogUI($"加工失败，{ErrMessge}");
                        return;
                    }

                    //d.点胶机需要验证是否超时，具体验证规则见方法以及配置
                    ErrCode = ValidWrokTimeOut(conn, codevalue);
                    if (ErrCode != 0) {
                        WriteInt16(replyPos, ErrCode);
                        return;
                    }
                    #endregion

                    //验证通过，允许加工
                    WriteInt16(replyPos, 1);
                    WriteLogUI($"加工开始，验证通过");

                } else if (result == 0) {
                    //复位上位机的信号
                    WriteInt16(replyPos, 0);
                    WriteLogUI($"加工复位");
                }
            }
        }

        /// <summary>
        /// 工艺节点验证
        /// </summary>
        /// <param name="conn"></param>
        /// <param name="technical"></param>
        /// <param name="IsPrev"></param>
        /// <param name="errMessge"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private short ValidNode(DbConnection conn, CksTechnicalFlowNodes technical, bool IsPrev, out string errMessge) {

            errMessge = "";

            //查询当前工艺节点的验证字段
            var fields = conn.Select<CksValidationField>()
                       .Where(a => a.BusinessType == 1 && a.BusinessId == technical.Id).OrderBy(a => a.Sequence)
                       .ToList();

            var errcode = 0;

            if (technical.Nullity) {
                //无论是前置节点还是当前节点，禁用就不进行验证
                return 0;
            }

            foreach (var item in fields) {

                if (item.IsPrimary) //是条码字段
                {
                    var primaryCode = base.GetMainCodeValue();
                    //var suffix = GetSuffix(primaryCode);
                    // var results = conn.Select<CksCollected>()
                    //.Where(a => a.TableId == technical.TableId && a.Suffix_4 == suffix && a.PrimaryCode == primaryCode && !a.IsDeleted)
                    //.Where(a => !a.Nullity.Value)//返工标记
                    //.OrderByDescending(a => a.CreatedTime).ToList(); //查询加工记录

                    var vtable = conn.Select<CksVirtualTable>().Where(o => o.Id == technical.TableId).ToOne();
                    var results = base.FreeSql.Queryable<object>().AsTable((type, oldName) => {
                        return vtable.TableName;
                    }).Where(" MainCode ='" + primaryCode + "' ")
                    .OrderBy(" CreateTime desc ")
                    .ToDataTable();

                    if (!technical.AllowReprocess)//不允许重复加工，查询当前站是否存在该条码
                    {

                        if (IsPrev) //当前是前置节点，前置记录不能为空
                        {
                            //if (results.Count == 0) {
                            if (results != null && results.Rows.Count == 0) {
                                errcode = 10;
                                errMessge = $"不允许跳工站({technical.Notes})加工";
                                break;
                            }

                        } else {

                            //if (results.FindAll(a => a.Result == 1).Count > 0) {
                            if (results.Rows.Count > 0 && results.Select("Nullity = 1").Count() > 0) { //处理返工
                                
                                WriteLogUI("【该工件为返工件】");

                            } else if (results.Select("Result = 1").Count() > 0) {
                                errcode = 11;
                                errMessge = $"本工位加工OK";
                                break;
                                //} else if (results.FindAll(a => a.Result == 2).Count > 0) {
                            } else if (results.Select("Result = 2 or Result = 0").Count() > 0) {
                                errcode = 13;
                                errMessge = $"本工位加工NG";
                                break;
                            }

                        }
                    }

                    if (!technical.AllowOkReprocess && !IsPrev) // 不允许OK件重复加工
                    {
                        //if (results.FindAll(a => a.Result == 1).Count > 0) {
                        if (results.Select("Result = 1").Count() > 0) {
                            errcode = 11;
                            errMessge = $"不允许OK件重复加工";
                            break;
                        }
                    }

                    if (technical.RequireOk && IsPrev) {
                        //if (results.First().Result != 1) {
                        if (results.Select("Result <> 1").Count() > 0) {
                            errcode = 12;
                            errMessge = $"上工站加工失败";
                            break;
                        }
                    }

                } else {

                    throw new Exception($"没有实现此验证方法");

                }
            }

            return (short)errcode;
        }

        /// <summary>
        /// 二维码产品防错校验
        /// </summary>
        /// <param name="conn"></param>
        /// <param name="code"></param>
        /// <param name="productTypeID"></param>
        /// <returns></returns>
        private bool ValidQrCode(DbConnection conn, string code, out int productTypeID) {

            productTypeID = 0;

            var list = conn.Select<CksProductTypeConfig>().ToList();

            var proItem = list.Where(o => new Regex(o.CodeRegExp).IsMatch(code)).FirstOrDefault();

            if (proItem != null) {
                productTypeID = proItem.TypeID;
                return true;
            }

            return false;
        }

        /// <summary>
        /// 写入托盘码和产品类型到PLC
        /// </summary>
        /// <param name="primaryCode"></param>
        /// <returns></returns>
        private bool WritePalletCodeAndProductTypeToPlc() {

            var result = DecodeValue.Value.ToInt();

            using (var ObjectCo = base.FreeSql.Ado.MasterPool.Get()) {

                var conn = ObjectCo.Value;
                var position = DecodeValue.Position;
                var act = conn.Select<CksPlcActions>()
                    .Where(a => a.ActionType == 2
                            && a.PlcId == position.PlcId
                            && a.ActionId == position.ActionId
                            && a.ActionKey == position.ActionKey
                            && a.ReadDB == position.BlockDb
                            )
                    .First();

                var replyPos = DecodeValue.GetReplyPosition(act.WriteDB, act.ActionId, position.ActionKey);
                var replyPalletCodePos = DecodeValue.GetReplyPosition(act.WriteDB, act.ActionId, "PalletCodeWriteToPlc");
                var replyProductTypePos = DecodeValue.GetReplyPosition(act.WriteDB, act.ActionId, "ProductTypeWriteToPlc");

                if (result == 1) {

                    string mainCodeValue = string.Empty;

                    #region 通过托盘码查询写入条码
                    var sysConfigService = App.GetService<ISysConfigService>();
                    var configValue = sysConfigService.GetSysConfigByKey("palletcodeverify");
                    var plcIDs = configValue.ConfigValue.Split(",");
                    if (plcIDs.Any(o => o == position.PlcId.ToString())) {
                        //通过托盘码在条码关系表中查找反向获取到产品码
                        //获取托盘码字段位置
                        var palletCodePosition = DecodeValue.GetPositionByActionKey(position, SubcodeBindingName.PalletCodeBindingActionKey);
                        if (palletCodePosition == null) {
                            WriteLogUI($"反写条码失败，未配置托盘码的ActionKey条码字段");
                            return false;
                        }

                        //获取托盘码值
                        var palletCodeValue = DecodeValue.GetLinkDecodeObject(palletCodePosition).CharString.TrimEnd();
                        //如果托盘码值为空，则不进行处理
                        if (string.IsNullOrEmpty(palletCodeValue)) {
                            WriteLogUI("反写条码失败，托盘条码值为空");
                            return false;
                        }

                        WriteLogUI($"反写条码开始，当前托盘码：{palletCodeValue}，正在查询...");
                        var barRelRec = conn.Select<CksBarcodeRelationshipRecord>().Where(o => o.Type == ZR.Model.Enums.eBarCodeRelType.PalletCode && o.SubcodeCode == palletCodeValue && o.IsDeleted == false).OrderBy(o => o.CreatedTime).ToList().LastOrDefault();
                        if (barRelRec == null) {
                            WriteLogUI($"反写条码失败，当前托盘码：{palletCodeValue}，未查询到托盘绑定关系");
                            return false;
                        }
                        WriteLogUI($"反写条码成功，当前托盘码：{palletCodeValue}，主产品码：{barRelRec.PrimaryCode}");
                        mainCodeValue = barRelRec.PrimaryCode;

                        //反写条码给PLC
                        var mainCodePosition = DecodeValue.GetPositionByActionKey(position, "PrimaryCode");
                        SiemensClient.SetValue(mainCodePosition, "");
                        SiemensClient.SetValue(mainCodePosition, mainCodeValue);
                        WriteLogUI($"反写条码结束，palletCodeValue：{palletCodeValue} mainCodeValue：{mainCodeValue}");
                    }
                    #endregion

                    mainCodeValue = GetMainCodeValue();//这句代码放在反写条码后面，确保获取到最新的条码值

                    #region 验证条码是否合格
                    if (string.IsNullOrEmpty(mainCodeValue)) {
                        WriteLogUI("主产品码验证失败，条码值为空");
                        WriteInt16(replyPos, 99);
                        SiemensClient.SetValue(replyPalletCodePos, "");
                        return false;
                    }

                    var productTypeID = 0;
                    //验证条码是否合格
                    if (!ValidQrCode(conn, mainCodeValue, out productTypeID)) {
                        //条码检测不合格
                        WriteInt16(replyPos, 98);
                        WriteLogUI($"主产品码验证失败，条码验证不合格");
                        return false;
                    }
                    #endregion


                    #region 写入产品类型
                    var cksSpotCheck = conn.Select<CksSpotCheck>().Where(a => a.PrimaryCode == mainCodeValue).ToList().LastOrDefault();
                    if (cksSpotCheck != null) {
                        var writeValue = cksSpotCheck.Type == 1 ? "101" : cksSpotCheck.Type == 2 ? "102" : "";
                        SiemensClient.SetValue(replyProductTypePos, writeValue);
                        WriteLogUI($"写入产品类型：{writeValue}");
                    } else {
                        SiemensClient.SetValue(replyProductTypePos, productTypeID.ToString());
                    }
                    #endregion


                    #region 写入托盘码
                    //如果条码值不为空，则查询托盘码
                    var barCodeRel = conn.Select<CksBarcodeRelationshipRecord>()
                        .Where(o => o.PrimaryCode == mainCodeValue && o.Type == eBarCodeRelType.PalletCode && o.IsDeleted == false)
                        .ToList()
                        .LastOrDefault();
                    //如果存在绑定关系，则直接写入
                    string palletCode = barCodeRel?.SubcodeCode;
                    if (!string.IsNullOrEmpty(palletCode)) {
                        SiemensClient.SetValue(replyPalletCodePos, "");
                        SiemensClient.SetValue(replyPalletCodePos, palletCode);
                        WriteLogUI($"写入托盘码成功，托盘码：{palletCode}");
                    } else {
                        SiemensClient.SetValue(replyPalletCodePos, "");
                        WriteLogUI($"写入托盘码失败，未查询到该条码绑定的托盘码关系");
                        return false;
                    }
                    #endregion

                } else {
                    base.WriteInt16(replyProductTypePos, 0);
                    WriteLogUI($"产品类型重置");

                    SiemensClient.SetValue(replyPalletCodePos, "");
                    WriteLogUI($"托盘码重置");
                }
            }

            return true;
        }

        /// <summary>
        /// 涂胶后超时验证
        /// </summary>
        /// <param name="conn"></param>
        /// <param name="primaryCode"></param>
        /// <returns></returns>
        private short ValidWrokTimeOut(DbConnection conn, string primaryCode) {
            try {
                var position = DecodeValue.Position;
                var sysConfigService = App.GetService<ISysConfigService>();
                var configValue = sysConfigService.GetSysConfigByKey("processTimeOutVerify");
                JArray jArray = JArray.Parse(configValue.ConfigValue);
                //[ { "CurVerifyStation": "121|1030", "NeedVerifyStation": "120", "TimeOutDuration": "180" }, { "CurVerifyStation": "140|1130", "NeedVerifyStation": "137,138", "TimeOutDuration": "180" } ]
                foreach (var item in jArray) {
                    //是否开启
                    var isEnabled = item["IsEnabled"].ToBool();

                    if (isEnabled) {

                        //当前待验证工位信息 121|1030
                        var curVerifyStationID = item["CurVerifyStation"].ToString().Split("|")[0].ToInt();
                        var curVerifyStationDBblock = item["CurVerifyStation"].ToString().Split("|")[1].ToInt();
                        //待验证工位信息 137
                        var needVerifyStationID = item["NeedVerifyStation"].ToString().Split("|")[0].ToInt();
                        var needVerifyStationDBblock = item["NeedVerifyStation"].ToString().Split("|")[1].ToInt();
                        //总时间
                        var timeOutDuration = item["TimeOutDuration"].ToInt();

                        if (position.PlcId == curVerifyStationID && position.BlockDb == curVerifyStationDBblock) {

                            //查询待验证工位的生产记录
                            var collectInfo = conn.Select<CksCollected, CksWorkRecord, PlcConfig>()
                                .LeftJoin((a, b, c) => a.WorkRecId == b.Id)
                                .LeftJoin((a, b, c) => a.PlcId == c.Id)
                                .Where((a, b, c) => a.PrimaryCode == primaryCode && a.PlcId == needVerifyStationID && a.ReadDB == needVerifyStationDBblock)
                                .ToList((a, b, c) => new { a.PlcId, c.WorkstationCode, a.WorkRecId, a.CreatedTime, b.ProductionStartTime, b.ProductionEndTime }).LastOrDefault();

                            //计算时间差
                            var diffTime = (int)(DateTime.Now - collectInfo.ProductionStartTime.Value).TotalSeconds;

                            if (diffTime > timeOutDuration) {
                                WriteLogUI($"验证时间防错失败，当前工位{curVerifyStationID}，验证工位{needVerifyStationID}-{collectInfo.WorkstationCode}超时，允许时间{timeOutDuration}秒，实际等待{diffTime}秒");
                                return 14;
                            } else {
                                WriteLogUI($"验证时间防错OK，当前工位{curVerifyStationID}，验证工位{needVerifyStationID}-{collectInfo.WorkstationCode}超时，允许时间{timeOutDuration}秒，实际等待{diffTime}秒");
                                return 0;
                            }
                        }
                    }

                }

            } catch (Exception ex) {

                WriteLogUI($"验证时间防错失败，{ex.Message}");

                return -1;
            }

            return 0;
        }


    }
}
